package client

import (
	"addsvc"
	"addsvc/internal/endpoint"
	"addsvc/internal/pb"
	"addsvc/internal/utils/method"
	"context"
	"errors"
	"io"

	kitEndpoint "github.com/go-kit/kit/endpoint"
	"github.com/go-kit/kit/sd"
	kitRPC "github.com/go-kit/kit/transport/grpc"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/trace"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/metadata"

	"github.com/go-kit/kit/sd/etcdv3"
	"github.com/go-kit/log"
)

func GRPC(prefix string, discoverys []string, tracer trace.Tracer, logger log.Logger) (addsvc.Service, error) {

	client, err := etcdv3.NewClient(context.Background(), discoverys, etcdv3.ClientOptions{})
	if err != nil {
		return nil, err
	}

	sum := makeEndpoint(
		prefix, client,
		method.Sum,
		logger, tracer,
		makeGRPCFactory(method.Sum),
	)
	concat := makeEndpoint(
		prefix, client,
		method.Concat,
		logger, tracer,
		makeGRPCFactory(method.Concat),
	)

	return &endpoint.Endpoints{
		SumPoint:    sum,
		ConcatPoint: concat,
	}, nil
}

func makeGRPCFactory(method method.Method) sd.Factory {
	encodeRequest, decodeResponse, grpcReply := getGRPCFunc(method)

	return func(instance string) (kitEndpoint.Endpoint, io.Closer, error) {
		conn, err := grpc.NewClient(instance, grpc.WithTransportCredentials(insecure.NewCredentials()))
		if err != nil {
			return nil, nil, err
		}
		sum := kitRPC.NewClient(
			conn,
			"pb.Service",
			string(method),
			encodeRequest,
			decodeResponse,
			grpcReply,
			kitRPC.ClientBefore(contextToGRPC()),
		).Endpoint()
		return sum, conn, nil
	}
}

func contextToGRPC() kitRPC.ClientRequestFunc {
	return func(ctx context.Context, md *metadata.MD) context.Context {
		carrier := propagation.MapCarrier{}
		propagator := otel.GetTextMapPropagator()
		propagator.Inject(ctx, carrier)
		for k, v := range carrier {
			md.Set(k, v)
		}
		return ctx
	}
}

func getGRPCFunc(m method.Method) (kitRPC.EncodeRequestFunc, kitRPC.DecodeResponseFunc, any) {
	switch m {
	case method.Sum:
		return encodeGRPCSumClientRequest, decodeGRPCSumClientResponse, pb.SumResponse{}
	case method.Concat:
		return encodeGRPCConcatClientRequest, decodeGRPCConcatClientResponse, pb.ConcatResponse{}
	}
	return nil, nil, nil
}

// encodeGRPCConcatClientRequest is encode a endpoint.ConcatRequest struct to the GRPC request.
// Primarily useful in a client.
func encodeGRPCConcatClientRequest(_ context.Context, r any) (any, error) {
	req := r.(endpoint.ConcatRequest)
	return &pb.ConcatRequest{A: req.A, B: req.B}, nil
}

// decodeGRPCConcatClientResponse is decode a GRPC request struct to the endpoint.ConcatResponse.
// Primarily useful in a client.
func decodeGRPCConcatClientResponse(_ context.Context, grpcResp any) (any, error) {
	resp := grpcResp.(*pb.ConcatResponse)
	var err error
	if resp.Err != "" {
		err = errors.New(resp.Err)
	}
	return endpoint.ConcatResponse{V: resp.V, Err: err}, nil
}

// encodeGRPCSumClientRequest is encode a endpoint.SumRequest struct to the GRPC request.
// Primarily useful in a client.
func encodeGRPCSumClientRequest(_ context.Context, grpcReq any) (any, error) {
	req := grpcReq.(endpoint.SumRequest)
	return &pb.SumRequest{A: int64(req.A), B: int64(req.B)}, nil
}

// decodeGRPCSumClientResponse is decode a GRPC request struct to the endpoint.SumResponse.
// Primarily useful in a client.
func decodeGRPCSumClientResponse(_ context.Context, grpcResp any) (any, error) {
	resp := grpcResp.(*pb.SumResponse)
	var err error
	if resp.Err != "" {
		err = errors.New(resp.Err)
	}
	return endpoint.SumResponse{V: int(resp.Sum), Err: err}, nil
}
